package com.ruoyi.learn.java.design.pattern.behavior;

/**
 * 模板方法模式 (Template Method Pattern) 示例：制作咖啡因饮料（咖啡和茶）。
 */
public class TemplateMethodPatternDemo {

    //region ========== 1. 抽象类 (Abstract Class) ==========

    /**
     * 抽象类：咖啡因饮料
     * 定义了制作咖啡因饮料的算法骨架（模板方法）。
     * 将变化的步骤（冲泡和添加调料）延迟到子类实现。
     */
    public abstract static class CaffeineBeverage {

        /**
         * 模板方法 (Template Method)
         * 定义了制作饮料的完整算法骨架。
         * 通常声明为 final，防止子类修改算法流程。
         */
        public final void prepareRecipe() {
            boilWater();           // 步骤1：煮沸水 (通用)
            brew();                // 步骤2：冲泡原料 (变化 - 由子类实现)
            pourInCup();           // 步骤3：倒入杯中 (通用)
            if (customerWantsCondiments()) { // 钩子方法决定是否执行
                addCondiments();   // 步骤4：添加调料 (变化 - 由子类实现)
            }
        }

        //region ========== 抽象操作 (Abstract Operations) ==========

        /**
         * 抽象方法：冲泡原料
         * 具体的冲泡方式由子类决定（如用沸水冲泡咖啡粉或茶叶）。
         */
        protected abstract void brew();

        /**
         * 抽象方法：添加调料
         * 具体的调料由子类决定（如加糖和牛奶，或加柠檬）。
         */
        protected abstract void addCondiments();

        //endregion

        //region ========== 具体操作 (Concrete Operations) ==========

        /**
         * 具体方法：煮沸水
         * 所有咖啡因饮料都需要煮沸水，实现是通用的。
         */
        protected void boilWater() {
            System.out.println("  > 步骤1：将水煮沸...");
        }

        /**
         * 具体方法：倒入杯中
         * 所有咖啡因饮料最后都要倒入杯中，实现是通用的。
         */
        protected void pourInCup() {
            System.out.println("  > 步骤3：将饮料倒入杯中...");
        }

        //endregion

        //region ========== 钩子方法 (Hook Method) ==========

        /**
         * 钩子方法 (Hook Method)
         * 在抽象类中提供了默认实现（默认返回 true）。
         * 子类可以覆盖此方法，以控制是否执行 addCondiments() 步骤。
         * @return true 表示客户想要调料，false 表示不想要。
         */
        protected boolean customerWantsCondiments() {
            return true; // 默认是添加调料
        }

        //endregion
    }

    //endregion

    //region ========== 2. 具体子类 (Concrete Class) ==========

    /**
     * 具体子类：咖啡
     * 实现了咖啡因饮料的抽象方法，定义了制作咖啡的具体步骤。
     */
    public static class Coffee extends CaffeineBeverage {

        @Override
        protected void brew() {
            System.out.println("  > 步骤2：用沸水冲泡咖啡粉...");
        }

        @Override
        protected void addCondiments() {
            System.out.println("  > 步骤4：加入糖和牛奶...");
        }

        // 可以选择性地覆盖钩子方法
        // 例如，提供一个方法让用户选择是否加调料
        // @Override
        // protected boolean customerWantsCondiments() {
        //     return getUserInput("是否要加糖和牛奶？(y/n) ");
        // }
    }

    /**
     * 具体子类：茶
     * 实现了咖啡因饮料的抽象方法，定义了制作茶的具体步骤。
     */
    public static class Tea extends CaffeineBeverage {

        @Override
        protected void brew() {
            System.out.println("  > 步骤2：用沸水浸泡茶叶...");
        }

        @Override
        protected void addCondiments() {
            System.out.println("  > 步骤4：加入柠檬...");
        }

        // 覆盖钩子方法，询问用户是否要加柠檬
        // 这里为了演示，我们模拟用户输入
        @Override
        protected boolean customerWantsCondiments() {
            // 模拟用户选择：茶不加调料
            System.out.println("  > 提示：本店特色茶不加任何调料，风味更佳。");
            return false;
        }
    }

    //endregion

    //region ========== 3. 具体子类扩展示例 ==========

    /**
     * 具体子类：无调料咖啡
     * 演示如何通过覆盖钩子方法来改变算法流程。
     */
    public static class BlackCoffee extends Coffee {
        // 继承了 Coffee 的 brew 和 addCondiments 实现

        @Override
        protected boolean customerWantsCondiments() {
            // 明确表示不想要任何调料（黑咖啡）
            System.out.println("  > 提示：您选择了纯正的黑咖啡，不加任何调料。");
            return false;
        }
    }

    //endregion

    //region ========== 4. 主函数 (Main) - 演示模板方法模式 ==========

    public static void main(String[] args) {
        System.out.println("=== 模板方法模式演示 ===\n");

        // 1. 制作一杯咖啡
        System.out.println("1. 制作一杯【咖啡】...");
        CaffeineBeverage coffee = new Coffee();
        coffee.prepareRecipe(); // 调用模板方法
        System.out.println(); // 换行

        // 2. 制作一杯茶
        System.out.println("2. 制作一杯【茶】...");
        CaffeineBeverage tea = new Tea();
        tea.prepareRecipe(); // 调用模板方法
        System.out.println(); // 换行

        // 3. 制作一杯黑咖啡
        System.out.println("3. 制作一杯【黑咖啡】...");
        CaffeineBeverage blackCoffee = new BlackCoffee();
        blackCoffee.prepareRecipe(); // 调用模板方法
        System.out.println(); // 换行

        // 4. 模板方法模式的优势
        System.out.println("--- 模板方法模式的优势 ---");
        System.out.println("• **代码复用**：将算法的通用部分（煮水、倒杯）");
        System.out.println("  集中在父类中实现，避免了子类间的代码重复。");
        System.out.println("• **扩展性**：通过继承，可以轻松添加新的饮料类型");
        System.out.println("  （如巧克力、果汁），只需实现抽象方法即可。");
        System.out.println("• **控制反转 (IoC)**：父类（框架）控制流程，");
        System.out.println("  子类（具体实现）填充细节。符合 '好莱坞原则'：");
        System.out.println("  '不要调用我们，我们会调用你'。");
        System.out.println("• **封装不变部分**：算法的结构（步骤顺序）");
        System.out.println("  被封装在父类的模板方法中，保证了流程的稳定性。");
        System.out.println("• **行为集中管理**：所有饮料的制作流程都遵循同一个模板，");
        System.out.println("  易于理解和维护。");

        // 5. 注意事项
        System.out.println("\n--- 注意事项 ---");
        System.out.println("• **继承的局限性**：模板方法模式依赖于继承。");
        System.out.println("  在 Java 中，类只能单继承，如果一个类已经有父类，");
        System.out.println("  就无法再继承 CaffeineBeverage。可以考虑使用组合模式");
        System.out.println("  或策略模式作为替代方案。");
        System.out.println("• **违反里氏替换原则 (LSP) 的风险**：");
        System.out.println("  如果子类覆盖了模板方法中调用的具体方法，");
        System.out.println("  可能会改变算法的预期行为。因此，模板方法通常声明为 final，");
        System.out.println("  而具体操作（boilWater, pourInCup）也应谨慎设计。");
        System.out.println("• **灵活性 vs. 约束**：虽然保证了流程，但也限制了灵活性。");
        System.out.println("  子类不能轻易改变步骤的顺序或添加新步骤。");
        System.out.println("  如果需要更大的灵活性，策略模式可能更合适。");
        System.out.println("• **'变与不变'的划分**：正确识别算法中哪些部分是'变'的，");
        System.out.println("  哪些是'不变'的，是成功应用此模式的关键。");

        // 6. 与策略模式的区别
        System.out.println("\n--- 与策略模式的区别 ---");
        System.out.println("• **模板方法模式**：基于**继承**。父类定义算法骨架，");
        System.out.println("  子类实现抽象方法。算法的结构是固定的，");
        System.out.println("  变化的是特定步骤的实现。适用于'同一个算法，不同实现'的场景。");
        System.out.println("• **策略模式**：基于**组合**。定义一系列算法，");
        System.out.println("  把它们封装在独立的类中，并使它们可以互换。");
        System.out.println("  算法的实现和使用算法的类是完全解耦的。");
        System.out.println("  适用于'多种算法，可以动态切换'的场景。");
        System.out.println("  例如：不同的支付方式（支付宝、微信、银行卡）。");

        // 7. 现实应用
        System.out.println("\n--- 现实应用 ---");
        System.out.println("• **GUI 框架**：窗口的生命周期方法（如 onInit(), onPaint(), onDestroy()）");
        System.out.println("  通常由框架定义模板，开发者在子类中重写这些方法。");
        System.out.println("• **单元测试框架**：JUnit 的 @Before, @Test, @After 注解");
        System.out.println("  可以看作是一种模板，框架控制测试的执行流程。");
        System.out.println("• **数据处理流水线**：读取数据 -> 处理数据 -> 写入数据，");
        System.out.println("  其中“处理数据”步骤可以由不同子类实现。");
        System.out.println("• **构建工具**：Maven/Gradle 的构建生命周期（compile, test, package）");
        System.out.println("  是一个模板，具体的编译、测试任务由插件实现。");
        System.out.println("• **Servlet**：`HttpServlet` 的 `service()` 方法是一个模板，");
        System.out.println("  根据 HTTP 方法调用 `doGet()`, `doPost()` 等抽象方法。");
    }

    //endregion
}
